package sdk

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	e "errors"
	"fmt"
	"github.com/astaxie/beego"
	"github.com/astaxie/beego/logs"
	"github.com/micro/go-micro/errors"
	"github.com/pydr/tools-golang"
	aai "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/aai/v20180522"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common"
	qerrs "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/errors"
	"github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common/profile"
	tts "github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/tts/v20190823"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"os"
	"strconv"
	"time"
	"unsafe"
)

// 请求参数组装
func (c *Client) genArgs(target map[string]interface{}) string {

	c.requstCount++

	//默认模型，必填参数
	if _, ok := target["ModelType"]; !ok {
		target["ModelType"] = 1
	}

	speed, ok := getArgValue("Speed", target)
	if ok {
		speedFloat, err := strconv.ParseFloat(speed, 32)
		if err == nil {
			target["Speed"] = speedFloat
		}
	}

	volume, ok := getArgValue("Volume", target)
	if ok {
		volumeFloat, err := strconv.ParseFloat(volume, 32)
		if err == nil {
			target["Volume"] = volumeFloat
		}
	}

	voiceType, ok := getArgValueNoFloat("VoiceType", target)
	if ok {
		voiceTypeInt, err := strconv.ParseInt(voiceType, 10, 32)
		if err == nil {
			target["VoiceType"] = voiceTypeInt
		}
	}

	sampleRate, ok := getArgValueNoFloat("SampleRate", target)
	if ok {
		sampleRateInt, err := strconv.ParseInt(sampleRate, 10, 32)
		if err == nil {
			target["SampleRate"] = sampleRateInt
		}
	}

	primaryLanguage, ok := getArgValueNoFloat("PrimaryLanguage", target)
	if ok {
		primaryLanguageInt, err := strconv.ParseInt(primaryLanguage, 10, 32)
		if err == nil {
			target["PrimaryLanguage"] = primaryLanguageInt
		}
	}

	target["SessionId"] = fmt.Sprintf("%v%d", c.dsid, c.requstCount)

	var t = int(time.Now().Unix())
	target["Timestamp"] = strconv.Itoa(t)

	argStr, _ := json.Marshal(target)

	return string(argStr)

}

func (c *Client) TencentTtsEx(args map[string]interface{}) (*string, error) {

	credential := common.NewCredential(
		c.secretId,
		c.secretKey,
	)

	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "tts.tencentcloudapi.com"
	client, _ := aai.NewClient(credential, "ap-shanghai", cpf)

	request := aai.NewTextToVoiceRequest()

	params := c.genArgs(args)
	logs.Debug("request args:%v", params)
	err := request.FromJsonString(params)
	if err != nil {
		panic(err)
	}

	response, err := client.TextToVoice(request)
	if v, ok := err.(*qerrs.TencentCloudSDKError); ok {
		logs.Error("An API error has returned: %s", v.Message)
		errJson, err := json.Marshal(v)
		if err != nil {
			return nil, e.New(v.Message)
		} else {
			return nil, e.New(string(errJson))
		}
	}

	if err != nil {
		return nil, err
	}

	return response.Response.Audio, nil
}

func (c *Client) TencentTtsNew(args map[string]interface{}) (*string, error) {

	credential := common.NewCredential(
		c.secretId,
		c.secretKey,
	)

	cpf := profile.NewClientProfile()
	cpf.HttpProfile.Endpoint = "tts.tencentcloudapi.com"
	client, _ := tts.NewClient(credential, "ap-shanghai", cpf)

	request := tts.NewTextToVoiceRequest()

	params := c.genArgs(args)
	logs.Debug("request args:%v", params)
	err := request.FromJsonString(params)
	if err != nil {
		panic(err)
	}

	response, err := client.TextToVoice(request)
	if v, ok := err.(*qerrs.TencentCloudSDKError); ok {
		logs.Error("An API error has returned: %s", v.Message)
		errJson, err := json.Marshal(v)
		if err != nil {
			return nil, e.New(v.Message)
		} else {
			return nil, e.New(string(errJson))
		}
	}

	if err != nil {
		return nil, err
	}

	return response.Response.Audio, nil
}

func getArgValue(k string, args map[string]interface{}) (string, bool) {

	if v, ok := args[k]; ok {
		switch v.(type) {
		case string:
			return v.(string), true
		case float32:
			return fmt.Sprintf("%.1f", v.(float32)), true
		case float64:
			return fmt.Sprintf("%.1f", v.(float64)), true
		case int:
			return fmt.Sprintf("%d", v.(int)), true
		case int64:
			return fmt.Sprintf("%d", v.(int64)), true
		case int32:
			return fmt.Sprintf("%d", v.(int32)), true
		}
	}

	return "", false
}

func getVoiceType(args map[string]interface{}) (string, bool) {

	if v, ok := args["VoiceType"]; ok {
		switch v.(type) {
		case string:
			return v.(string), true
		case float32:
			return fmt.Sprintf("%.0f", v.(float32)), true
		case float64:
			return fmt.Sprintf("%.0f", v.(float64)), true
		case int:
			return fmt.Sprintf("%d", v.(int)), true
		case int64:
			return fmt.Sprintf("%d", v.(int64)), true
		case int32:
			return fmt.Sprintf("%d", v.(int32)), true
		}
	}

	return "", false
}

func getArgValueNoFloat(k string, args map[string]interface{}) (string, bool) {

	if v, ok := args[k]; ok {
		switch v.(type) {
		case string:
			return v.(string), true
		case float32:
			return fmt.Sprintf("%.0f", v.(float32)), true
		case float64:
			return fmt.Sprintf("%.0f", v.(float64)), true
		case int:
			return fmt.Sprintf("%d", v.(int)), true
		case int64:
			return fmt.Sprintf("%d", v.(int64)), true
		case int32:
			return fmt.Sprintf("%d", v.(int32)), true
		}
	}

	return "", false
}

func Round(f float64, n int) float64 {

	n10 := math.Pow10(n)
	return math.Trunc((f+0.5/n10)*n10) / n10
}

func (c *Client) GetTtsFlowArgs(args map[string]interface{}) *ttsParam {

	newReqArgs := &ttsParam{
		AppId:           c.appId,
		SecretId:        c.secretId,
		Codec:           "pcm",
		PrimaryLanguage: 1,
		Action:          "TextToStreamAudio",
		Timestamp:       int(time.Now().Unix()),
		Expired:         int(time.Now().Unix() + 3600),
		SessionId:       fmt.Sprintf("%v%d", c.dsid, c.requstCount),
		ModelType:       1,
		Volume:          0,
		Speed:           0,
		ProjectId:       0,
		VoiceType:       0,
		SampleRate:      8000,
	}

	v, ok := getArgValue("ModelType", args)
	if ok {
		vi, err := strconv.Atoi(v)
		if err == nil {
			newReqArgs.ModelType = int64(vi)
		}
	}

	v, ok = getArgValue("Speed", args)
	if ok {

		vi, err := strconv.ParseFloat(v, 64)
		if err == nil {
			newReqArgs.Speed = Round(vi, 1)
		}
	}

	v, ok = getVoiceType(args)
	if ok {

		vi, err := strconv.Atoi(v)
		if err == nil {
			newReqArgs.VoiceType = vi
		}

		//logs.Debug("get voiceType:%v,convert:%v", v, vi)
	}

	v, ok = getArgValue("Volume", args)
	if ok {

		vi, err := strconv.ParseFloat(v, 64)
		if err == nil {
			newReqArgs.Volume = Round(vi, 1)
		}

	}

	v, ok = getArgValue("ProjectId", args)
	if ok {
		vi, err := strconv.Atoi(v)
		if err == nil {
			newReqArgs.ProjectId = vi
		}
	}

	v, ok = getArgValueNoFloat("PrimaryLanguage", args)
	if ok {
		vi, err := strconv.Atoi(v)
		if err == nil {
			newReqArgs.PrimaryLanguage = vi
		} else {
			logs.Debug("convert PrimaryLanguage failed:%v", err)
		}
	}

	v, ok = getArgValueNoFloat("SampleRate", args)
	if ok {
		vi, err := strconv.Atoi(v)
		if err == nil {
			newReqArgs.SampleRate = vi
		}
	}

	v, ok = getArgValue("Text", args)
	if !ok {
		logs.Error("request is no text string:%v", args)

	}
	newReqArgs.Text = v
	return newReqArgs
}

func (c *Client) FlowTtsConvert(SampleRate int, fileName string, args map[string]interface{}) error {

	newReqArgs := c.GetTtsFlowArgs(args)

	reqSortStr := strSortFlow(*newReqArgs)

	url := beego.AppConfig.String("ttsSrv")
	authStr := c.genFlowSign("POST", url, reqSortStr, c.secretKey)

	//logs.Debug("生成签名串:%v", authStr)
	reqBody, err := json.Marshal(*newReqArgs)
	if err != nil {
		logs.Error("json failed:%v", err)
		return errors.New("19101", err.Error(), 500)
	}

	logs.Debug("sign:%v,request json:%v", authStr, string(reqBody))
	body := bytes.NewReader(reqBody)

	req, err := http.NewRequest("POST", url, body)
	if err != nil {
		logs.Error("make request failed: %v,args:%v", err, args)
		return errors.New("19102", err.Error(), 500)
	}
	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Authorization", authStr)

	response, res := tools.Request(c.httpClient, req, 3)
	if res == false {
		logs.Error("call api failed -> %v,args:%v", url, args)
		return errors.New("19103", err.Error(), 500)
	}
	defer response.Body.Close()

	buff := bytes.NewBuffer([]byte{})
	written, err := io.Copy(buff, response.Body)
	if err != nil {
		logs.Error("invalid body data: %v,args:%v", err, args)
		return errors.New("19102", err.Error(), 500)
	}

	if response.StatusCode != 200 {
		logs.Error("request failed,http code:%v", response.StatusCode)
		return errors.New("19101", "http server failed", int32(response.StatusCode))
	}

	dst, err := os.OpenFile(fileName, os.O_RDWR|os.O_CREATE, 0777)
	if err != nil {
		logs.Error("open file[%s] failed:%v,args:%v", fileName, err, args)
		return errors.New("19102", err.Error(), 500)
	}

	defer dst.Close()

	logs.Debug("response :%v, save file [%s] size[%d] success!", response.StatusCode, fileName, written)
	if written < 512 {
		dst.Close()
		logs.Error("请求：%v ,TTS args:%v 失败, 文件名:%v", url, string(reqBody), fileName)

		_, err := ioutil.ReadFile(fileName)
		if err != nil {
			logs.Error("打开文件失败:%v", err)
		}
		os.Rename(fileName, fileName+"_err_"+strconv.Itoa(c.requstCount))

		return e.New(buff.String())
	}

	if SampleRate == 8000 {
		writeFileHead8K(dst, int(written))
	} else {
		writeFileHead(dst, int(written))
	}

	_, err = io.Copy(dst, buff)
	dst.Sync()

	return nil
}

func isLittleEndian() bool {
	var i int32 = 0x01020304
	u := unsafe.Pointer(&i)
	pb := (*byte)(u)
	b := *pb
	return (b == 0x04)
}

func writeintes(n int, b byte, buff *bytes.Buffer) error {
	switch b {
	case 1:
		tmp := int8(n)

		if isLittleEndian() {
			binary.Write(buff, binary.LittleEndian, &tmp)
		} else {
			binary.Write(buff, binary.BigEndian, &tmp)
		}

		return nil
	case 2:
		tmp := int16(n)

		if isLittleEndian() {
			binary.Write(buff, binary.LittleEndian, &tmp)
		} else {
			binary.Write(buff, binary.BigEndian, &tmp)
		}

		return nil
	case 3, 4:
		tmp := int32(n)

		if isLittleEndian() {
			binary.Write(buff, binary.LittleEndian, &tmp)
		} else {
			binary.Write(buff, binary.BigEndian, &tmp)
		}

		return nil
	}

	return fmt.Errorf("IntToBytes b param is invaild")
}

func writeFileHead(dst *os.File, fileSize int) {

	buff := bytes.NewBuffer([]byte{})

	buff.Write([]byte("RIFF"))

	//总文件长度
	writeintes(fileSize+40, 4, buff)

	buff.Write([]byte("WAVE"))
	buff.Write([]byte("fmt "))

	writeintes(16, 4, buff)

	//编码格式 1-PCM
	writeintes(1, 2, buff)

	//声道：1 - 单声道，2 - 双声道
	writeintes(1, 2, buff)

	//采样率
	writeintes(16000, 4, buff)

	//音频数据传送速率,采样率×每次采样大小 sampleRate * (bitsPerSample / 8) * channels
	writeintes(64000, 4, buff)

	//每次采样的大小 = 采样精度*声道数/8(单位是字节)
	writeintes(4, 2, buff)

	//采样精度 16bit
	writeintes(16, 2, buff)

	buff.Write([]byte("data"))

	//音频数据长度
	writeintes(fileSize, 4, buff)

	_, err := dst.Seek(0, 0)
	if err != nil {
		logs.Error("file seek failed:%v", err)
	}

	dst.Write(buff.Bytes())
}

func writeFileHead8K(dst *os.File, fileSize int) {

	buff := bytes.NewBuffer([]byte{})

	buff.Write([]byte("RIFF"))

	//总文件长度
	writeintes(fileSize+40, 4, buff)

	buff.Write([]byte("WAVE"))
	buff.Write([]byte("fmt "))

	writeintes(16, 4, buff)

	//编码格式 1-PCM
	writeintes(1, 2, buff)

	//声道：1 - 单声道，2 - 双声道
	writeintes(1, 2, buff)

	//采样率
	writeintes(8000, 4, buff)

	//音频数据传送速率,采样率×每次采样大小 sampleRate * (bitsPerSample / 8) * channels
	writeintes(32000, 4, buff)

	//每次采样的大小 = 采样精度*声道数/8(单位是字节)
	writeintes(4, 2, buff)

	//采样精度 16bit
	writeintes(16, 2, buff)

	buff.Write([]byte("data"))

	//音频数据长度
	writeintes(fileSize, 4, buff)

	_, err := dst.Seek(0, 0)
	if err != nil {
		logs.Error("file seek failed:%v", err)
	}

	dst.Write(buff.Bytes())
}
